/*!
 * UnitTest tool
 * 2011.03.06
 */
#ifndef UNITTEST_H_
#define UNITTEST_H_

#include <string>
#include <list>
#include <set>
#include <map>
#include <vector>
#include <exception>
#include <stdarg.h>

class TestResult;
class TestFailure;
class TestListener;

extern int unittestlib_loaded __attribute__ ((weak));

/*!
 * \brief メソッドテストアダプタ生成マクロ
 *   Suite()関数内で使用し、メソッドを呼び出すテストインスタンスを生成
 * \param[in] type クラス名
 * \param[in] method メソッド名
 */
#define TEST_CALLER(type, method)                       \
  TestCaller<type>(#type "::" #method, &type::method)

//
// テストクラス自動登録機能
//
/*!
 * \brief テストクラス自動登録マクロ
 * \param[in] type テストクラス型名 (TestCaseのサブクラス)
 * テストクラスの実装側グローバルスコープで宣言する
 * 登録先レジストリ: all
 */
#define TEST_REGISTRATION(type)                                     \
  static AutoRegister< type > MAKE_UNIQUE_NAME(AutoRegisterObject)

/*!
 * \brief テストクラス自動登録マクロ
 * \param[in] type テストクラス型名 (TestCaseのサブクラス)
 * テストクラスの実装側グローバルスコープで宣言する
 * 登録先レジストリ: all + default
 */
#define TEST_REGISTRATION_DEFAULT(type)                                 \
  static AutoRegister< type > MAKE_UNIQUE_NAME(AutoRegisterObject_)("default")

/*!
 * \brief テストクラス自動登録マクロ
 * \param[in] type テストクラス型名 (TestCaseのサブクラス)
 * \param[in] suite テストスイート名
 * テストクラスの実装側グローバルスコープで宣言する
 * 登録先レジストリ:all + テストスイート名
 */
#define TEST_REGISTRATION_NAMED(type, suite)                            \
  static AutoRegister< type > MAKE_UNIQUE_NAME(AutoRegisterObject_)(suite)

/*!
 * \brief テストクラス自動登録マクロ
 * \param[in] type テストクラス型名 (TestCaseのサブクラス)
 * テストクラスの実装側グローバルスコープで宣言する
 * 登録先レジストリ:all + テストクラス名
 */
#define TEST_REGISTRATION_CLASS(type)                                   \
  static AutoRegister< type > MAKE_UNIQUE_NAME(AutoRegisterObject_)(#type)

//
// テスト判定用マクロ
//
/*!
 * \brief テスト判定マクロ
 * \param[in] cond 条件式
 * condの評価結果がfalseの場合、テスト関数は失敗し以降の処理を行わない
 */
#define TEST_ASSERT(cond) Asserter::Fail(__FILE__, __LINE__, #cond, !(cond))

/*!
 * \brief テスト判定マクロ
 * \param[in] cond 条件式
 * \param[in] msg エラー内容メッセージ
 * condの評価結果がfalseの場合、テスト関数は失敗し以降の処理を行わない
 */
#define TEST_ASSERT_MESSAGE(cond, msg)              \
  Asserter::Fail(__FILE__, __LINE__, msg, !(cond))

/*!
 * \brief テスト判定マクロ
 * \param[in] expected 期待値
 * \param[in] actual 結果値
 * 期待値と結果値が異なる場合、テスト関数は失敗し以降の処理を行わない
 */
#define TEST_ASSERT_EQUAL(expected, actual)                 \
  Asserter::Equal(__FILE__, __LINE__, (expected), (actual))

/*!
 * \brief テスト判定マクロ
 * \param[in] msg エラー内容メッセージ
 * 必ず失敗し以降の処理を行わない
 */
#define TEST_FAIL(msg) Asserter::Fail(__FILE__, __LINE__, msg)

/*!
 * \brief テスト判定マクロ
 * \param[in] cond 条件式
 * 必ず失敗し以降の処理を行わない システムエラー関係で使用する
 */
#define TEST_ERROR(msg) Asserter::Error(__FILE__, __LINE__, msg)

//
// スタブライブラリ制御用ファンクタコンテナ
//

/*!
 * スタブライブラリ戻り値設定用マクロ
 * \key スタブ関数名
 * \val スタブ関数戻り値
 */
#define SET_STUB_RETVAL(key, val)                       \
  SET_STUB_VALUE(key, val, FunctorContainer::MODE_SET)
#define COMBINE_STUB_RETVAL(key, val)                       \
  SET_STUB_VALUE(key, val, FunctorContainer::MODE_COMBINE)

#define SET_STUB_VALUE(key, val, mode)                                  \
  class MAKE_UNIQUE_NAME(__temp_class) : public Functor {               \
  public:                                                               \
    MAKE_UNIQUE_NAME(__temp_class)(void *v) : v_(v) {}                  \
    void *operator()(int, va_list) {                                    \
      return v_;                                                        \
    }                                                                   \
  private:                                                              \
    void *v_;                                                           \
  }; FunctorRegistry::Push(key, new MAKE_UNIQUE_NAME(__temp_class)((void*)val), mode)

/*!
 * スタブライブラリ戻り値&出力パラメータ設定用マクロ
 * \key スタブ関数名
 * \val スタブ関数戻り値
 * \argnum 出力パラメータ位置(1-origin)
 * \data 出力パラメータ設定データ
 * \size 出力パラメータ設定データサイズ
 */
#define SET_STUB_OUTPARAM(key, val, pos, data, size)                    \
  SET_STUB_PARAM(key, val, pos, data, size, true, true, FunctorContainer::MODE_SET)
#define COMBINE_STUB_OUTPARAM(key, val, pos, data, size)                \
  SET_STUB_PARAM(key, val, pos, data, size, true, true, FunctorContainer::MODE_COMBINE)

/*!
 * スタブライブラリ戻り値設定&入力パラメータ取得マクロ
 * \key スタブ関数名
 * \val スタブ関数戻り値
 * \argnum 入力パラメータ位置(1-origin)
 * \data 入力パラメータ取得バッファ
 * \size 入力パラメータ取得サイズ
 */
#define SET_STUB_INPARAM_P(key, val, pos, data, size)                   \
  SET_STUB_PARAM(key, val, pos, data, size, false, true, FunctorContainer::MODE_SET)
#define COMBINE_STUB_INPARAM_P(key, val, pos, data, size)               \
  SET_STUB_PARAM(key, val, pos, data, size, false, true, FunctorContainer::MODE_COMBINE)
#define SET_STUB_INPARAM(key, val, pos, data, size)                     \
  SET_STUB_PARAM(key, val, pos, data, size, false, false, FunctorContainer::MODE_SET)
#define COMBINE_STUB_INPARAM(key, val, pos, data, size)                 \
  SET_STUB_PARAM(key, val, pos, data, size, false, false, FunctorContainer::MODE_COMBINE)

/*!
 * スタブライブラリ戻り値設定&入出力パラメータ操作
 * \key スタブ関数名
 * \val スタブ関数戻り値
 * \argnum 入力パラメータ位置(1-origin)
 * \data 入力パラメータ取得バッファ
 * \size 入力パラメータ取得サイズ
 * \setflag  出力パラメータ設定(true) 入力パラメータ取得(false)
 * \pflg  引数型がポインタ(true) 引数型が値(false)
 * \mode Functor追加/結合モード
 */
#define SET_STUB_PARAM(key, val, pos, data, size, setflag, pflg, mode)  \
  class MAKE_UNIQUE_NAME(__temp_class) : public Functor {               \
  public:                                                               \
  MAKE_UNIQUE_NAME(__temp_class)(void* v, int n, void* p, size_t sz,    \
                                 bool set, bool isP)                    \
    : v_(v), n_(n), p_(p), sz_(sz), set_(set), isP_(isP) {              \
      if (set_) { p_ = malloc(sz); memcpy(p_, p, sz); }                 \
      TEST_ASSERT(n_ > 0); }                                            \
    void *operator()(int argnum, va_list va) { void* p;                 \
      for(int i = 0; i < n_-1; i++) va_arg(va, void *);                 \
      if (isP_) p = *(va_arg(va, void**));                              \
      else      p = va_arg(va, void*);                                  \
      if (set_) memcpy(p, p_, sz_);                                     \
      else      memcpy(p_, p, sz_);                                     \
      return v_;                                                        \
    }                                                                   \
  ~MAKE_UNIQUE_NAME(__temp_class)() { if (set_) free(p_); }             \
  private:                                                              \
    void* v_;                                                           \
    int n_;                                                             \
    void* p_;                                                           \
    size_t sz_;                                                         \
    bool set_;                                                          \
    bool isP_;                                                          \
  }; FunctorRegistry::Push(key, new MAKE_UNIQUE_NAME(__temp_class)((void*)val, pos, data, size, setflag, pflg), mode)

/*!
 * スタブライブラリ戻り値取得用マクロ
 * \type 戻り値型
 * \defval デフォルト値
 * \num スタブ関数引数の数
 * \args... スタブ関数の引数(アドレス渡しにすること)
 * \return 取得した戻り値(デフォルト値またはファンクタの戻り値)
 */
#define GET_STUB_VALUE(type, defval, num, args...)                      \
  ((unittestlib_loaded) ? (type)FunctorRegistry::Call(__func__, (void *)defval, num, ## args) : (type)((void*)defval))

/*!
 * 一般ファンクタ設定マクロ
 * \key コンテナ登録キー
 * \functor ファンクタ
 */
#define SET_FUNCTOR(key, functor)                                   \
  FunctorRegistry::Push(key, functor, FunctorContainer::MODE_SET)
#define COMBINE_FUNCTOR(key, functor)                                   \
  FunctorRegistry::Push(key, functor, FunctorContainer::MODE_COMBINE)

/*!
 * ファンクタ削除マクロ
 * \key コンテナ登録キー
 */
#define REMOVE_FUNCTOR(key) FunctorRegistry::Remove(key)

///////////////////////////////////////////////////////////////////////////////
// テストクラス
///////////////////////////////////////////////////////////////////////////////
/*!
 * テストインタフェース
 */
class Test
{
 public:
  virtual ~Test() {}
  virtual void Run(TestResult* result) = 0;
  virtual int CountTestCases() const = 0;
  virtual const char* GetName() const = 0;
};

/*!
 * \brief 基本テストクラス
 * 本クラスのサブクラスを定義し、テスト関数を定義する
 */
class TestCase : public Test
{
 public:
  TestCase();
  TestCase(const char* name);
  virtual ~TestCase();
  virtual void Run(TestResult *result);
  virtual int CountTestCases() const;
  virtual const char* GetName() const;
  /*!
   * \brief インスタンス生成関数
   * テストクラス自動登録を行う場合、インスタンス生成関数を記述する必要がある
   */
  // static Test* MakeTest();
  /*!
   * \brief テスト初期化関数
   * ・テスト実行前に呼び出される。テスト前に必要な初期化処理を記述する
   */
  virtual void SetUp();
  /*!
   * \brief テスト終了処理関数
   * ・テスト終了時に呼び出される。リソース開放等を記述する
   */
  virtual void TearDown();
 protected:
  /*!
   * \brief テスト内容を記述する関数
   * ・利用者はRunTest()をオーバーライドする必要がある
   * ・TEST_CALLERを使用する場合はreturnのみのstub関数でもよい
   *   (上記マクロはRunTestを生成するアダプタです)
   */
  virtual void RunTest() = 0;
 private:
  void StartTest(TestResult* result);
  void EndTest(TestResult* result);
  TestCase(const TestCase& rhs);
  TestCase& operator=(const TestCase& rhs);
 private:
  const char* name_;
};

/*!
 * テストスイートクラス
 */
class TestSuite : public Test
{
 public:
  TestSuite(const char* name = "");
  virtual ~TestSuite();
  virtual void Run(TestResult* result);
  virtual int CountTestCases() const;
  virtual const char* GetName() const;
  void AddTest(Test* test);
 private:
  TestSuite(const TestSuite& rhs);
  TestSuite& operator=(const TestSuite& rhs);
 private:
  const char* name_;
  std::list<Test*> tests_;
};

/*!
 * テストメソッドアダプタテンプレート
 */
template <class T>
class TestCaller : public TestCase
{
 public:
  typedef void (T::*Method)();
  TestCaller(const char* name, Method method)
   : TestCase(name), instance_(new T()), method_(method) {}
  ~TestCaller() { delete instance_; }
  void RunTest() { (instance_->*method_)(); }
  void SetUp() { instance_->SetUp(); }
  void TearDown() { instance_->TearDown(); }
 private:
  TestCaller(const TestCaller& rhs);
  TestCaller& operator=(const TestCaller& rhs);
 private:
  T* instance_;
  Method method_;
};

/*!
 * 繰り返し機能付きテスト
 */
class TestRepeater : public Test
{
 public:
  TestRepeater(Test* test, int repeat);
  ~TestRepeater();
  virtual void Run(TestResult* result);
  virtual int CountTestCases() const;
  virtual const char* GetName() const;
 private:
  TestRepeater(const TestRepeater& rhs);
  TestRepeater& operator=(const TestRepeater& rhs);
 private:
  Test* test_;
  const int repeat_;
};

///////////////////////////////////////////////////////////////////////////////
// ファンクタコンテナ (stub関数制御用途)
///////////////////////////////////////////////////////////////////////////////
/*!
 * ファンクタインタフェース
 */
class Functor {
 public:
  virtual void *operator()(int argnum, va_list va) = 0;
  virtual ~Functor(){}
  const char* key_;
};

/*!
 * ファンクタコンテナ
 */
class FunctorContainer {
 public:
  enum {
    MODE_SET = 0,
    MODE_COMBINE, // 関数合成
  };
 FunctorContainer(const char* key, Functor* functor, int mode)
   : key_(key), functor_(functor), mode_(mode) { functor_->key_ = key_; }
  ~FunctorContainer() { delete functor_; }
  const char* key_;
  Functor* functor_;
  int mode_;
};

/*!
 * ファンクタレジスタ
 */
class FunctorRegistry
{
 public:
  FunctorRegistry();
  ~FunctorRegistry();
  static void Push(const char *key, Functor *functor,
                   int mode = FunctorContainer::MODE_SET);
  static void *Call(const char *key, void *retval, int argnum, ...);
  static void Remove(const char *key);
 private:
  static FunctorContainer *Pop(const char *key);
  static void Clear();
  static std::vector<FunctorContainer*> registry_;
};

/*!
 * ファンクタ繰り返し用アダプタ
 */
class RepeatFunctor : public Functor {
 public:
  RepeatFunctor(Functor* f, int count) : f_(f), count_(count), owner_(true) {}
  RepeatFunctor(RepeatFunctor* rhs) : f_(rhs->f_), count_(rhs->count_), owner_(true) { rhs->owner_ = false; }
  virtual void *operator()(int argnum, va_list va) {
    if (count_ > 0) { --count_; SET_FUNCTOR(key_, new RepeatFunctor(this)); }
    if (count_ < 0) { SET_FUNCTOR(key_, new RepeatFunctor(this)); }
    return (*f_)(argnum, va);
  }
  virtual ~RepeatFunctor() { if(owner_) delete f_; }
  Functor* f_;
  int count_;
  bool owner_;
};

///////////////////////////////////////////////////////////////////////////////
// 例外通知
///////////////////////////////////////////////////////////////////////////////
/*!
 * 例外クラス
 */
class Exception : public std::exception
{
 public:
  Exception(const char* fileName, int line_, const char* message);
  virtual ~Exception() throw();
  const char* FileName() const;
  int Line() const;
  const char* Message() const;
  Exception(const Exception& rhs);
 private:
  Exception& operator=(const Exception& rhs);
 private:
  const char *fileName_;
  int line_;
  std::string message_;
};

/*!
 * テスト失敗通知用例外クラス
 */
class FailException : public Exception
{
 public:
  FailException(const char* fileName, int line_, const char* message);
  virtual ~FailException() throw();
};

/*!
 * テスト実行エラー通知用例外クラス
 */
class ErrorException : public Exception
{
 public:
  ErrorException(const char* fileName, int line_, const char* message);
  virtual ~ErrorException() throw();
};

/*!
 * 例外生成ユーティリティ
 */
class Asserter
{
 public:
  static void Fail(const char* fileName, int line, const char* msessage,
                   bool failed = true);
  static void Error(const char* fileName, int line, const char* message);
  static void Equal(const char* fileName, int line, int expected, int actual);
  static void Equal(const char* fileName, int line, long expected,long actual);
  static void Equal(const char* fileName, int line, const char* expected,
                    const char* actual);
};

///////////////////////////////////////////////////////////////////////////////
// テストファクトリ、レジストリ
///////////////////////////////////////////////////////////////////////////////
/*!
 * テストファクトリインタフェース
 */
class TestFactory
{
 public:
  virtual ~TestFactory() {}
  virtual Test* MakeTest() = 0;
};

/*!
 * テストファクトリテンプレートクラス
 */
template<class T>
class TestSuiteFactory : public TestFactory
{
 public:
  virtual Test *MakeTest() { return T::MakeTest(); }
};

/*!
 * テストファクトリレジストリ
 */
class TestFactoryRegistry : public TestFactory
{
 public:
  class RegistryMap
  {
  public:
    RegistryMap();
    ~RegistryMap();
    static TestFactoryRegistry* GetRegistry(const char* name, bool creae);
  private:
    static RegistryMap* GetInstance();
    TestFactoryRegistry* GetInternalRegistry(const char* name, bool create);
    std::map<std::string, TestFactoryRegistry*> registries_;
  };
 public:
  TestFactoryRegistry(const char* name);
  virtual ~TestFactoryRegistry();
  virtual Test* MakeTest();
  static TestFactoryRegistry* GetRegistry(const char* name = "all");
  static bool IsAvailable(const char* name);
  void RegistFactory(TestFactory* factory);
  void UnRegistFactory(TestFactory* factory);
 private:
  const char* name_;
  std::set<TestFactory*> factories_;
};

/*!
 * テストファクトリ自動登録レジストリ
 */
template<class T>
class AutoRegister
{
 public:
  AutoRegister()
    : registry_(TestFactoryRegistry::GetRegistry()) {
    registry_->RegistFactory(&factory_);
  }
  AutoRegister(const char* name)
    : registry_(TestFactoryRegistry::GetRegistry(name)) {
    registry_->RegistFactory(&factory_);
  }
  ~AutoRegister() {
    //registry_->UnRegistFactory(&factory_); SIGSEGV対策
  }
 private:
  TestFactoryRegistry* registry_;
  TestSuiteFactory<T> factory_;
};

/*!
 * テストオブジェクト登録クラス
 */
class TestObjectRegistry
{
 public:
  TestObjectRegistry();
  ~TestObjectRegistry();
  void AddTest(const char* name, Test* test);
  bool SetAvailable(const char* name, bool value = true);
  void SetAllAvailable(bool value = true);
  bool HasAvailable() const;
  void Run(TestResult* result);
 public:
  class Entry {
  public:
    Entry(const char* name, Test* test, bool available = false);
    ~Entry();
    const char* GetName() const;
    Test* GetTest() const;
    bool IsAvailable() const;
    void SetAvailable(bool value = true);
  private:
    std::string name_;
    Test* test_;
    bool available_;
  };
 private:
  std::list<Entry*> entries_;
};

///////////////////////////////////////////////////////////////////////////////
// テスト結果
///////////////////////////////////////////////////////////////////////////////
/*!
 * テスト結果保持クラス
 */
class TestResult
{
 public:
  TestResult();
  ~TestResult();
  void AddListener(TestListener* listener);
  void RemoveListener(TestListener* listener);
  void StartTest(Test* test);
  void EndTest(Test* test);
  void Reset();
  void Stop();
  bool ShouldStop() const;
  const std::list<TestFailure*>& Failures() const;
  const std::list<TestFailure*>& Errors() const;
  void AddFailure(Test* test, const Exception& e);
  void AddError(Test* test, const Exception& e);
  int RunCount() const;
  int FailureCount() const;
  int ErrorCount() const;
  bool WasSuccessful() const;
 private:
  TestResult(const TestResult& rhs);
  TestResult& operator=(const TestResult& rhs);
 private:
  std::list<TestListener*> listeners_;
  std::list<TestFailure*> failures_;
  std::list<TestFailure*> errors_;
  int runCount_;
  bool shouldStop_;
};

/*!
 * テストエラー結果
 */
class TestFailure
{
 public:
  TestFailure(Test* test, const char* message, const char* fileName, int line);
  ~TestFailure();
  Test* FailedTest() const;
  const char* What() const;
  const char* FileName() const;
  int Line() const;
 private:
  Test* test_;
  std::string message_;
  const char* fileName_;
  int line_;
};

/*!
 * テストリスナインタフェース
 */
class TestListener
{
 public:
  virtual ~TestListener() {}
  virtual void StartTest(Test* test) = 0;
  virtual void EndTest(Test* test) = 0;
  virtual void AddFailure(const TestFailure* failure) = 0;
  virtual void AddError(const TestFailure* error) = 0;
};

/*!
 * 簡易テストリスナ
 */
class SimpleListener : public TestListener
{
 public:
  SimpleListener();
  ~SimpleListener();
  void StartTest(Test* test);
  void EndTest(Test* test);
  void AddFailure(const TestFailure* failure);
  void AddError(const TestFailure* failure);
};

/*!
 * ログ機能付きテストリスナ
 */
class Logger : public TestListener
{
 public:
  Logger(FILE *fp);
  ~Logger();
  void StartTest(Test* test);
  void EndTest(Test* test);
  void AddFailure(const TestFailure* failure);
  void AddError(const TestFailure* error);
  void PrintResult(const TestResult* result) const;
  void PrintHeader() const;
  static Logger* OpenFile(const char* fileName, const char* mode = "a");
 private:
  FILE* fp_;
  bool hasError_;
  void PrintTestResult(Test* test, bool ok) const;
  void Close();
};

///////////////////////////////////////////////////////////////////////////////
// テスト実行
///////////////////////////////////////////////////////////////////////////////
/*!
 * テスト実行管理クラス
 */
class TestRunner
{
 public:
  TestRunner();
  ~TestRunner();
  int Run(int argc, char** argv);
  void AddTest(Test* test);
  void AddTest(const char* name, Test* test);
 private:
  int ParseArg(int argc, char** argv);
  bool SetTest(const char* name);
  int RunTest();
  void Wait() const;
  void PrintUsage(const char* command) const;
  void PrintResult(const TestResult* result) const;
 private:
  bool wait_;
  bool verbose_;
  const char* logfile_;
  std::set<const char*> autoRegistryTests_;
  TestObjectRegistry manualRegistryTests_;
};

///////////////////////////////////////////////////////////////////////////////
// その他 ユーティリティ
///////////////////////////////////////////////////////////////////////////////
/*!
 * ユーティリティ
 */
class TestUtil
{
 public:
  static void PrintFailureDetail(FILE* fp, const TestFailure* failure);
  static void PrintResult(FILE* fp, const TestResult* result);
  static const char* TrimFileName(const char* fileName);
};

/*!
 * スマートポインタ
 */
template <class T>
class SmartPtr
{
 public:
  explicit SmartPtr(T* p = 0) throw() : p_(p) {}
  ~SmartPtr() throw() { delete p_; }
  T& operator*() const throw() { return *p_; }
  T* operator->() const throw() { return p_; }
  T* Get() const throw() { return p_; }
  T* Release() throw() { T* tmp = p_; p_ = 0; return tmp; }
  void Reset(T* p = 0) throw() { if (p != p_) { delete p_; p_ = p; } }
 private:
  SmartPtr(SmartPtr& rhs) throw();
  SmartPtr& operator=(SmartPtr& rhs) throw();
 private:
  T* p_;
};

/*!
 * ユニーク名作成マクロ
 */
#define JOIN_XY(x, y) x##y
#define JOIN_SYMBOL(symbol1, symbol2) JOIN_XY(symbol1, symbol2)
#define MAKE_UNIQUE_NAME(prefix) JOIN_SYMBOL(prefix, __LINE__)

#endif // UNITTEST_H_
